package it.infodea.tapestrydea.components;

import it.infodea.tapestrydea.support.DeaEventConstants;
import it.infodea.tapestrydea.support.DeaUtils;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ComponentEventCallback;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.EventConstants;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.FormValidationControl;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.annotations.IncludeStylesheet;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.beaneditor.BeanModel;
import org.apache.tapestry5.corelib.components.ActionLink;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.internal.beaneditor.BeanModelUtils;
import org.apache.tapestry5.internal.util.Holder;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.BeanModelSource;

@IncludeStylesheet(value = "../assets/styles/tapestrydea.css")
public class BeanEditFormDea implements FormValidationControl {

	/**
	 * The Identifiable object to be edited. This will be read when the
	 * component renders and updated when the form for the component is
	 * submitted. Typically, the container will listen for a "prepare" event, in
	 * order to ensure that a non-null value is ready to be read or updated.
	 * Often, the BeanEditFormDea can create the object as needed (assuming a
	 * public, no arguments constructor). The object property defaults to a
	 * property with the same name as the component id.
	 */
	@Property
	@Parameter(required = true, autoconnect = true)
	private Object object;

	/**
	 * Set true for confirm mode
	 */
	@Parameter(value = "false")
	private boolean confirmMode;

	/**
	 * A comma-separated list of property names to be retained from the
	 * {@link org.apache.tapestry5.beaneditor.BeanModel}. Only these properties
	 * will be retained, and the properties will also be reordered. The names
	 * are case-insensitive.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String include;

	/**
	 * A comma-separated list of property names to be added to the
	 * {@link org.apache.tapestry5.beaneditor.BeanModel}.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String add;

	/**
	 * A comma-separated list of property names to be removed from the
	 * {@link org.apache.tapestry5.beaneditor.BeanModel}. The names are
	 * case-insensitive.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String exclude;

	/**
	 * A comma-separated list of property names indicating the order in which
	 * the properties should be presented. The names are case insensitive. Any
	 * properties not indicated in the list will be appended to the end of the
	 * display order.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String reorder;

	/**
	 * The model that identifies the parameters to be edited, their order, and
	 * every other aspect. If not specified, a default bean model will be
	 * created from the type of the object bound to the object parameter.
	 */
	@Property
	@Parameter
	private BeanModel<?> model;

	/**
	 * The label of the legend in the fieldset form. The field is adding only if
	 * the legendLabel is setted.
	 */
	@SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String legendLabel;

	/**
	 * Block to show inside form
	 */
	@SuppressWarnings("unused")
	@Parameter(value = "block:internalBlock")
	@Property(write = false)
	private Block internalBlock;

	/**
	 * The text label for the submit button or yes of the form, by default "Save".
	 */
	@SuppressWarnings("unused")
	@Parameter(value = "message:submit-label", defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String submitLabel;

	/**
	 * The text label for the cancel button or no of the form, by default "Cancel".
	 */
	@SuppressWarnings("unused")
	@Parameter(value = "message:cancel-label", defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String cancelLabel;

	/**
	 * The confirm operation message 
	 */
	@SuppressWarnings("unused")
	@Parameter(value = "message:confirm-delete-label", defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String confirmMessage;
	
	/**
	 * Set true to hide the element. By default false
	 */
	@SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private boolean hideElement = false;
	
	 /**
     * Binding the zone parameter will cause the form submission to be handled as an Ajax request that updates the
     * indicated zone.  Often a Form will update the same zone that contains it.
     */
    @SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
    private String zone;

	@Inject
	private ComponentResources resources;

	@Inject
	private BeanModelSource beanModelSource;

	@Inject
	private Block editBlock;

	@Inject
	private Block confirmBlock;

	@SuppressWarnings("unused")
	@Component
	private ActionLink noLink;

	@SuppressWarnings("unused")
	@Component
	private ActionLink yesLink;

	@Component(id = "deaform")
	private Form deaform;

	private boolean onCancelEvent = false;

	void onPrepareFromDeaForm() {
		resources.triggerEvent(EventConstants.PREPARE, null, null);
		if (model == null) {
			model = beanModelSource.createEditModel(object.getClass(), resources.getContainerMessages());
		}
		BeanModelUtils.modify(model, add, include, exclude, reorder);
	}

	@OnEvent(component = "noLink")
	Object onNoLink() {
		final Holder<Object> matchesHolder = Holder.create();
		ComponentEventCallback<Object> callback = new ComponentEventCallback<Object>() {
			public boolean handleResult(Object resultToHandle) {
				matchesHolder.put(resultToHandle);
				return true;
			}
		};
		trigger(DeaEventConstants.CANCEL, callback, object);
		return matchesHolder.get();
	}

	@OnEvent(component = "yesLink")
	Object onYesLink() {
		final Holder<Object> matchesHolder = Holder.create();
		ComponentEventCallback<Object> callback = new ComponentEventCallback<Object>() {
			public boolean handleResult(Object resultToHandle) {
				matchesHolder.put(resultToHandle);
				return true;
			}
		};
		trigger(DeaEventConstants.CONFIRM, callback, object);
		return matchesHolder.get();
	}

	/**
	 * Event triggered on cancel button event.
	 */
	@OnEvent(value = "selected", component = "cancel")
	void onCancel() {
		clearErrors();
		onCancelEvent = true;
	}

	/**
	 * Event triggered on form validation event.
	 */
	@OnEvent(value = "validateForm", component = "deaform")
	void onValidate() {
		if (!onCancelEvent) {
			final Holder<String> matchesHolder = Holder.create();
			ComponentEventCallback<?> callback = new ComponentEventCallback<String>() {
				public boolean handleResult(String resultToHandle) {
					matchesHolder.put(resultToHandle);
					return true;
				}
			};
			trigger(DeaEventConstants.VALIDATE, callback, object);
			String errorMessage = matchesHolder.get();
			if (!DeaUtils.isEmpty(errorMessage)) {
				recordError(errorMessage);
			}
		}
	}

	/**
	 * Event triggered on form failure event.
	 */
	@OnEvent(value = "failure", component = "deaform")
	void onSubmitFailure() {
		trigger(DeaEventConstants.FAILURE, object);
	}

	/**
	 * Event triggered on form success event.
	 */
	@OnEvent(value = "success", component = "deaform")
	Object onSubmitSuccess() {
		final Holder<Object> matchesHolder = Holder.create();
		ComponentEventCallback<Object> callback = new ComponentEventCallback<Object>() {
			public boolean handleResult(Object resultToHandle) {
				matchesHolder.put(resultToHandle);
				return true;
			}
		};
		if (onCancelEvent) {
			trigger(DeaEventConstants.CANCEL, callback);
		} else {
			trigger(DeaEventConstants.SAVE, callback, object);
		}
		return matchesHolder.get();
	}

	/*PUBLIC*/

	public Block getInitialMode() {
		if (confirmMode) {
			return confirmBlock;
		}
		return editBlock;
	}

	public void clearErrors() {
		deaform.clearErrors();
	}

	public boolean getHasErrors() {
		return deaform.getHasErrors();
	}

	public boolean isValid() {
		return deaform.isValid();
	}

	public void recordError(String errorMessage) {
		deaform.recordError(errorMessage);
	}

	public void recordError(Field field, String errorMessage) {
		deaform.recordError(field, errorMessage);
	}

	/*PRIVATE*/

	private void trigger(String eventType, Object... params) {
		trigger(eventType, null, params);
	}

	private void trigger(String eventType, ComponentEventCallback<?> callback, Object... params) {
		resources.triggerEvent(eventType, params, callback);
	}

}
